package pad.com.haidiyun.www.widget;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnInfoListener;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.MediaController;
import android.widget.MediaController.MediaPlayerControl;

import java.io.IOException;


/**
 * VideoView1 is used to play video, just like
 * {@link android.widget.VideoView VideoView1}. We define a custom view, because
 * we could not use {@link android.widget.VideoView VideoView1} in ListView. <br/>
 * VideoViews inside ScrollViews do not scroll properly. Even if you use the
 * workaround to set the background color, the MediaController2 does not scroll
 * along with the VideoView1. Also, the scrolling video looks horrendous with the
 * workaround, lots of flickering.
 * 
 * @author leo
 * 
 */
@SuppressLint("NewApi")
public class VideoView extends TextureView implements MediaPlayerControl {

	private static final String TAG = "VideoView1";

	// all possible internal states
	private static final int STATE_ERROR = -1;
	private static final int STATE_IDLE = 0;
	private static final int STATE_PREPARING = 1;
	private static final int STATE_PREPARED = 2;
	private static final int STATE_PLAYING = 3;
	private static final int STATE_PAUSED = 4;
	private static final int STATE_PLAYBACK_COMPLETED = 5;

	// currentState is a VideoView1 object's current state.
	// targetState is the state that a method caller intends to reach.
	// For instance, regardless the VideoView1 object's current state,
	// calling pause() intends to bring the object to a target state
	// of STATE_PAUSED.
	private int mCurrentState = STATE_IDLE;
	private int mTargetState = STATE_IDLE;

	// Stuff we need for playing and showing a video
	private MediaPlayer mMediaPlayer;
	private int mVideoWidth;
	private int mVideoHeight;
	private int mSurfaceWidth;
	private int mSurfaceHeight;
	private SurfaceTexture mSurfaceTexture;
	private Surface mSurface;
	private MediaController mMediaController;
	private OnCompletionListener mOnCompletionListener;
	private MediaPlayer.OnPreparedListener mOnPreparedListener;

	private OnErrorListener mOnErrorListener;
	private OnInfoListener mOnInfoListener;

	private int mSeekWhenPrepared; // recording the seek position while
									// preparing
	private int mCurrentBufferPercentage;
	private int mAudioSession;
	private Uri mUri;

	private Context mContext;

	public VideoView(final Context context) {
		super(context);
		mContext = context;
		initVideoView();
	}

	public VideoView(final Context context, final AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		initVideoView();
	}

	public VideoView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		mContext = context;
		initVideoView();
	}

	public void initVideoView() {
		mVideoHeight = 0;
		mVideoWidth = 0;
		setBackgroundColor(getResources().getColor(android.R.color.transparent));
		setFocusable(false);
		setSurfaceTextureListener(mSurfaceTextureListener);
	}

	public int resolveAdjustedSize(int desiredSize, int measureSpec) {
		int result = desiredSize;
		int specMode = MeasureSpec.getMode(measureSpec);
		int specSize = MeasureSpec.getSize(measureSpec);

		switch (specMode) {
		case MeasureSpec.UNSPECIFIED:
			/*
			 * Parent says we can be as big as we want. Just don't be larger
			 * than max size imposed on ourselves.
			 */
			result = desiredSize;
			break;

		case MeasureSpec.AT_MOST:
			/*
			 * Parent says we can be as big as we want, up to specSize. Don't be
			 * larger than specSize, and don't be larger than the max size
			 * imposed on ourselves.
			 */
			result = Math.min(desiredSize, specSize);
			break;

		case MeasureSpec.EXACTLY:
			// No choice. Do what we are told.
			result = specSize;
			break;
		}
		return result;
	}

	public void setVideoPath(String path) {
		Log.d(TAG, "Setting video path to: " + path);
		setVideoURI(Uri.parse(path));
	}

	public void setVideoURI(Uri _videoURI) {
		mUri = _videoURI;
		mSeekWhenPrepared = 0;
		requestLayout();
		invalidate();
		openVideo();
	}

	public Uri getUri() {
		return mUri;
	}

	public void setSurfaceTexture(SurfaceTexture _surfaceTexture) {
		mSurfaceTexture = _surfaceTexture;
	}

	public void openVideo() {
		if ((mUri == null) || (mSurfaceTexture == null)) {
			Log.d(TAG, "Cannot open video, uri or surface texture is null.");
			return;
		}
		// Tell the music playback service to pause
		// TODO: these constants need to be published somewhere in the
		// framework.
		Intent i = new Intent("com.android.music.musicservicecommand");
		i.putExtra("command", "pause");
		mContext.sendBroadcast(i);
		release(false);
		try {
			mSurface = new Surface(mSurfaceTexture);
			mMediaPlayer = new MediaPlayer();
			if (mAudioSession != 0) {
				mMediaPlayer.setAudioSessionId(mAudioSession);
			} else {
				mAudioSession = mMediaPlayer.getAudioSessionId();
			}

			mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
			mMediaPlayer.setOnCompletionListener(mCompleteListener);
			mMediaPlayer.setOnPreparedListener(mPreparedListener);
			mMediaPlayer.setOnErrorListener(mErrorListener);
			mMediaPlayer.setOnInfoListener(mOnInfoListener);
			mMediaPlayer.setOnVideoSizeChangedListener(mVideoSizeChangedListener);

			mMediaPlayer.setSurface(mSurface);
			mCurrentBufferPercentage = 0;
			mMediaPlayer.setDataSource(mContext, mUri);

			mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
			mMediaPlayer.setScreenOnWhilePlaying(true);
			mMediaPlayer.prepare();
//			mMediaPlayer.prepareAsync();
			mCurrentState = STATE_PREPARING;
		} catch (IllegalStateException e) {
			mCurrentState = STATE_ERROR;
			mTargetState = STATE_ERROR;
			Log.d(TAG, e.getMessage()); // TODO auto-generated catch block
		} catch (IOException e) {
			mCurrentState = STATE_ERROR;
			mTargetState = STATE_ERROR;
			Log.d(TAG, e.getMessage()); // TODO auto-generated catch block
		}
	}

	public void stopPlayback() {
		if (mMediaPlayer != null) {
			mMediaPlayer.stop();
			mMediaPlayer.release();
			mMediaPlayer = null;
			if (null != mMediaControllListener) {
				mMediaControllListener.onStop();
			}
		}
	}

	public void setMediaController(MediaController controller) {
		if (mMediaController != null) {
			mMediaController.hide();
		}
		mMediaController = controller;
		attachMediaController();
	}
	
	private void attachMediaController() {
		if (mMediaPlayer != null && mMediaController != null) {
			mMediaController.setMediaPlayer(this);
			View anchorView = this.getParent() instanceof View ? (View) this.getParent() : this;
			mMediaController.setAnchorView(anchorView);
			mMediaController.setEnabled(isInPlaybackState());
		}
	}

	private void release(boolean cleartargetstate) {
		Log.d(TAG, "Releasing media player.");
		if (mMediaPlayer != null) {
			mMediaPlayer.reset();
			mMediaPlayer.release();
			mMediaPlayer = null;
			mCurrentState = STATE_IDLE;
			if (cleartargetstate) {
				mTargetState = STATE_IDLE;
			}
		} else {
			Log.d(TAG, "Media player was null, did not release.");
		}
	}
	 
	@Override
	protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
		// Will resize the view if the video dimensions have been found.
		// video dimensions are found after onPrepared has been called by
		// MediaPlayer
		int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
		int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
		/*if ((mVideoWidth > 0) && (mVideoHeight > 0)) {
			if ((mVideoWidth * height) > (width * mVideoHeight)) {
				Log.d(TAG, "Video too tall, change size.");
				height = (width * mVideoHeight) / mVideoWidth;
			} else if ((mVideoWidth * height) < (width * mVideoHeight)) {
				Log.d(TAG, "Video too wide, change size.");
				width = (height * mVideoWidth) / mVideoHeight;
			} else {
				Log.d(TAG, "Aspect ratio is correct.");
			}
		}*/
		setMeasuredDimension(width, height);
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (isInPlaybackState() && mMediaController != null) {
			toggleMediaControlsVisiblity();
		}
		return false;
	}

	@Override
	public boolean onTrackballEvent(MotionEvent ev) {
		if (isInPlaybackState() && mMediaController != null) {
			toggleMediaControlsVisiblity();
		}
		return false;
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK && keyCode != KeyEvent.KEYCODE_VOLUME_UP && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN
				&& keyCode != KeyEvent.KEYCODE_VOLUME_MUTE && keyCode != KeyEvent.KEYCODE_MENU && keyCode != KeyEvent.KEYCODE_CALL
				&& keyCode != KeyEvent.KEYCODE_ENDCALL;
		if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {
			if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
				if (mMediaPlayer.isPlaying()) {
					pause();
					mMediaController.show();
				} else {
					start();
					mMediaController.hide();
				}
				return true;
			} else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
				if (!mMediaPlayer.isPlaying()) {
					start();
					mMediaController.hide();
				}
				return true;
			} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
				if (mMediaPlayer.isPlaying()) {
					pause();
					mMediaController.show();
				}
				return true;
			} else {
				toggleMediaControlsVisiblity();
			}
		}

		return super.onKeyDown(keyCode, event);
	}

	private void toggleMediaControlsVisiblity() {
		if (mMediaController.isShowing()) {
			mMediaController.hide();
		} else {
			mMediaController.show();
		}
	}
	
	
	public void start() {
		
		// This can potentially be called at several points, it will go through
		// when all conditions are ready
		// 1. When setting the video URI
		// 2. When the surface becomes available
		// 3. From the activity
		if (isInPlaybackState()) {
			mMediaPlayer.start();
			mCurrentState = STATE_PLAYING;
			if (null != mMediaControllListener) {
				mMediaControllListener.onStart();
			}
		} else {
			Log.d(TAG, "Could not start. Current state " + mCurrentState);
		}
		mTargetState = STATE_PLAYING;
	}

	public void pause() {
		if (isInPlaybackState()) {
			if (mMediaPlayer.isPlaying()) {
				mMediaPlayer.pause();
				mCurrentState = STATE_PAUSED;
				if (null != mMediaControllListener) {
					mMediaControllListener.onPause();
				}
			}
		}
		mTargetState = STATE_PAUSED;
	}

	public void suspend() {
		release(false);
	}

	public void resume() {
		openVideo();
	}

	@Override
	public int getDuration() {
		if (isInPlaybackState()) {
			return mMediaPlayer.getDuration();
		}

		return -1;
	}

	@Override
	public int getCurrentPosition() {
		if (isInPlaybackState()) {
			return mMediaPlayer.getCurrentPosition();
		}
		return 0;
	}

	@Override
	public void seekTo(int msec) {
		if (isInPlaybackState()) {
			mMediaPlayer.seekTo(msec);
			mSeekWhenPrepared = 0;
		} else {
			mSeekWhenPrepared = msec;
		}
	}

	@Override
	public boolean isPlaying() {
		return isInPlaybackState() && mMediaPlayer.isPlaying();
	}

	@Override
	public int getBufferPercentage() {
		if (mMediaPlayer != null) {
			return mCurrentBufferPercentage;
		}
		return 0;
	}

	private boolean isInPlaybackState() {
		return ((mMediaPlayer != null) && (mCurrentState != STATE_ERROR) && (mCurrentState != STATE_IDLE) && (mCurrentState != STATE_PREPARING));
	}

	@Override
	public boolean canPause() {
		return false;
	}

	@Override
	public boolean canSeekBackward() {
		return false;
	}

	@Override
	public boolean canSeekForward() {
		return false;
	}

	@Override
	public int getAudioSessionId() {
		if (mAudioSession == 0) {
			MediaPlayer foo = new MediaPlayer();
			mAudioSession = foo.getAudioSessionId();
			foo.release();
		}
		return mAudioSession;
	}

	// Listeners
	private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() {
		@Override
		public void onBufferingUpdate(final MediaPlayer mp, final int percent) {
			mCurrentBufferPercentage = percent;
		}
	};

	private OnCompletionListener mCompleteListener = new OnCompletionListener() {
		@Override
		public void onCompletion(final MediaPlayer mp) {
			mCurrentState = STATE_PLAYBACK_COMPLETED;
			mTargetState = STATE_PLAYBACK_COMPLETED;
			mSurface.release();

			if (mMediaController != null) {
				mMediaController.hide();
			}

			if (mOnCompletionListener != null) {
				mOnCompletionListener.onCompletion(mp);
			}

			if (mMediaControllListener != null) {
				mMediaControllListener.onComplete();
			}
		}
	};

	private MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
		@Override
		public void onPrepared(final MediaPlayer mp) {
			mCurrentState = STATE_PREPARED;

			if (mOnPreparedListener != null) {
				mOnPreparedListener.onPrepared(mMediaPlayer);
			}
			if (mMediaController != null) {
				mMediaController.setEnabled(true);
			}

			mVideoWidth = mp.getVideoWidth();
			mVideoHeight = mp.getVideoHeight();

			int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be
													// changed after seekTo()
													// call
			if (seekToPosition != 0) {
				seekTo(seekToPosition);
			}

			requestLayout();
			invalidate();
			if ((mVideoWidth != 0) && (mVideoHeight != 0)) {
				if (mTargetState == STATE_PLAYING) {
					mMediaPlayer.start();
					if (null != mMediaControllListener) {
						mMediaControllListener.onStart();
					}
				}
			} else {
				if (mTargetState == STATE_PLAYING) {
					mMediaPlayer.start();
					if (null != mMediaControllListener) {
						mMediaControllListener.onStart();
					}
				}
			}
		}
	};

	private MediaPlayer.OnVideoSizeChangedListener mVideoSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() {
		@Override
		public void onVideoSizeChanged(final MediaPlayer mp, final int width, final int height) {
			mVideoWidth = mp.getVideoWidth();
			mVideoHeight = mp.getVideoHeight();
			if (mVideoWidth != 0 && mVideoHeight != 0) {
				requestLayout();
			}
		}
	};

	private OnErrorListener mErrorListener = new OnErrorListener() {
		@Override
		public boolean onError(final MediaPlayer mp, final int what, final int extra) {
			Log.d(TAG, "Error: " + what + "," + extra);
			mCurrentState = STATE_ERROR;
			mTargetState = STATE_ERROR;

			if (mMediaController != null) {
				mMediaController.hide();
			}

			/* If an error handler has been supplied, use it and finish. */
			if (mOnErrorListener != null) {
				if (mOnErrorListener.onError(mMediaPlayer, what, extra)) {
					return true;
				}
			}

			/*
			 * Otherwise, pop up an error dialog so the user knows that
			 * something bad has happened. Only try and pop up the dialog if
			 * we're attached to a window. When we're going away and no longer
			 * have a window, don't bother showing the user an error.
			 */
			if (getWindowToken() != null) {

//				new AlertDialog.Builder(mContext).setMessage("Error: " + what + "," + extra).setPositiveButton("OK", new DialogInterface.OnClickListener() {
//					public void onClick(DialogInterface dialog, int whichButton) {
//						/*
//						 * If we get here, there is no onError listener, so at
//						 * least inform them that the video is over.
//						 */
//						if (mOnCompletionListener != null) {
//							mOnCompletionListener.onCompletion(mMediaPlayer);
//						}
//					}
//				}).setCancelable(false).show();
			}
			return true;
		}
	};

	SurfaceTextureListener mSurfaceTextureListener = new SurfaceTextureListener() {
		@Override
		public void onSurfaceTextureAvailable(final SurfaceTexture surface, final int width, final int height) {
			Log.d(TAG, "onSurfaceTextureAvailable.");
			mSurfaceTexture = surface;
			openVideo();
		}

		@Override
		public void onSurfaceTextureSizeChanged(final SurfaceTexture surface, final int width, final int height) {
			Log.d(TAG, "onSurfaceTextureSizeChanged: " + width + '/' + height);
			mSurfaceWidth = width;
			mSurfaceHeight = height;
			boolean isValidState = (mTargetState == STATE_PLAYING);
			boolean hasValidSize = (mVideoWidth == width && mVideoHeight == height);
			if (mMediaPlayer != null && isValidState && hasValidSize) {
				if (mSeekWhenPrepared != 0) {
					seekTo(mSeekWhenPrepared);
				}
				start();
			}
		}

		@Override
		public boolean onSurfaceTextureDestroyed(final SurfaceTexture surface) {

			mSurface = null;
			if (mMediaController != null)
				mMediaController.hide();
			release(true);
			return true;
		}

		@Override
		public void onSurfaceTextureUpdated(final SurfaceTexture surface) {

		}
	};

	/**
	 * Register a callback to be invoked when the media file is loaded and ready
	 * to go.
	 * 
	 * @param l
	 *            The callback that will be run
	 */
	public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) {
		mOnPreparedListener = l;
	}

	/**
	 * Register a callback to be invoked when the end of a media file has been
	 * reached during playback.
	 * 
	 * @param l
	 *            The callback that will be run
	 */
	public void setOnCompletionListener(OnCompletionListener l) {
		mOnCompletionListener = l;
	}

	/**
	 * Register a callback to be invoked when an error occurs during playback or
	 * setup. If no listener is specified, or if the listener returned false,
	 * VideoView1 will inform the user of any errors.
	 * 
	 * @param l
	 *            The callback that will be run
	 */
	public void setOnErrorListener(OnErrorListener l) {
		mOnErrorListener = l;
	}

	/**
	 * Register a callback to be invoked when an informational event occurs
	 * during playback or setup.
	 * 
	 * @param l
	 *            The callback that will be run
	 */
	public void setOnInfoListener(OnInfoListener l) {
		mOnInfoListener = l;
	}

	public static interface MediaControllListener {
		public void onStart();

		public void onPause();

		public void onStop();

		public void onComplete();
	}

	MediaControllListener mMediaControllListener;

	public void setMediaControllListener(MediaControllListener mediaControllListener) {
		mMediaControllListener = mediaControllListener;
	}

	
	@Override
	public void setVisibility(int visibility) {
		System.out.println("setVisibility: "+ visibility);
		super.setVisibility(visibility);
	}
}
